﻿using System;
using System.Linq;
using NodeSource.DataProvider;
using NodeSource.Executor;
using NodeSource.Result;

namespace NodeSource
{
    public class NodeSource
    {
        private readonly IDataProvider _dataProvider;
        private readonly Connection _connection;
        private String _alias;

        public readonly String Name;
        public String Alias
        {
            get { return String.IsNullOrEmpty(_alias) ? Name : _alias; }
            set { _alias = value; }
        }

        public DataProviderType DataProviderType
        {
            get { return _dataProvider.DataProviderType; }
        }

        public bool CanBeQueriedInGroup
        {
            get { return _dataProvider.CanBeQueriedInGroup; }
        }

        public NodeSource(Connection connection, string name)
        {
            _connection = connection;
            Name = name;
            _dataProvider = DataProviderFactory.Create(connection);
        }

        public ResultSet Query(Query query)
        {
            if (!_dataProvider.IsActive())
                _dataProvider.Open();

            var result = _dataProvider.Query(query);

            if (result.IsSuccess)
            {
                var table = result.Tables.First();
                table = query.Splits.Aggregate(table, (current, split) => Join(current, split.Value));
                result.Tables[0] = table;
            }

            _dataProvider.Close();
            return result;
        }

        private Table Join(Table left, QuerySplit querySplit)
        {
            var resultTable = new Table();

            var result = querySplit.Query.NodeSources.First().Value.Query(querySplit.Query);
            var expr = querySplit.JoinPredicate.Expression as BinaryExpression;
            var leftExpr = expr.LeftOperand;
            var rightExpr = expr.RightOperand;
            var oOperator = expr.Operator;

            if (result.IsSuccess)
            {
                var right = result.Tables.First();

                resultTable.Columns.AddRange(left.Columns);
                resultTable.Columns.AddRange(right.Columns);

                switch (querySplit.SplitJoinType)
                {
                    case JoinType.Inner:

                        object leftVal = null, rightVal = null;
                        FieldType leftFieldType, rightFieldType;

                        foreach (var leftRow in left.Rows)
                        {
                            if(leftExpr is ConstantExpression)
                            {
                                leftVal = ((ConstantExpression) leftExpr).Value;
                                leftFieldType = ((ConstantExpression)leftExpr).FieldType;
                            }
                            else
                            {
                                var leftParamExpr = leftExpr as ParameterExpression;
                                var keyValue = leftRow.GetValue(leftParamExpr.Name.Split('.')[1]);
                                leftVal = keyValue.Value;
                                leftFieldType = keyValue.Key.FieldType;
                            }

                            foreach (var rightRow in right.Rows)
                            {
                                if (rightExpr is ConstantExpression)
                                {
                                    rightVal = ((ConstantExpression)rightExpr).Value;
                                    rightFieldType = ((ConstantExpression)rightExpr).FieldType;
                                }
                                else
                                {
                                    var rightParamExpr = rightExpr as ParameterExpression;
                                    var keyValue = rightRow.GetValue(rightParamExpr.Name.Split('.')[1]);
                                    rightVal = keyValue.Value;
                                    rightFieldType = keyValue.Key.FieldType;
                                }

                                if (Compare(leftVal, rightVal, leftFieldType, rightFieldType, oOperator))
                                {
                                    var row = resultTable.NewRow();
                                    foreach (var data in leftRow.Values)
                                    {
                                        row.SetValue(data.Key, data.Value);
                                    }
                                    foreach (var data in rightRow.Values)
                                    {
                                        row.SetValue(data.Key, data.Value);
                                    }
                                }
                            }
                        }

                        break;
                    case JoinType.Left:
                        break;
                    case JoinType.Right:
                        break;
                    case JoinType.Cross:
                        break;
                }
            }

            return resultTable;
        }

        private bool Compare(object left, object right, FieldType leftFieldType, FieldType rightFieldType, Operator oOperator)
        {
            int res = Field.Compare(left, right, leftFieldType, rightFieldType, oOperator);
            switch (oOperator)
            {
                case Operator.EqualTo:
                    return res == 0;
                case Operator.LessThan:
                    return res < 0;
                case Operator.GreaterThan:
                    return res > 0;
                case Operator.LessThanOrEqualTo:
                    return res <= 0;
                case Operator.GreaterThanOrEqualTo:
                    return res >= 0;
            }

            return false;
        }

        /// <summary>
        /// Two node sources are same if the connection resolver is same
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return _connection.GetHashCode();
        }
    }
}
